/*
 * Copyright 2006-2007 S W McIlroy
 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 * you may not use  this file  except in  compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed  under the  License is distributed on an "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 * implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.mockfu;

import static org.mockfu.Exceptions.*;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * A <tt>{@link MockControl}</tt> is an object responsible for building and verifying the
 * expectations of a mock.
 *
 * @param <M> the mock object type
 */
public class MockControl<M>
{
    private static final ThreadLocal<LinkedList<CurrentControlElement>> currentControl
            = new ThreadLocal<LinkedList<CurrentControlElement>>();
    private static final ThreadLocal<MockControl<?>> lastCreated = new ThreadLocal<MockControl<?>>();

    private final Class<M> klass;
    private final Thread createdFrom = Thread.currentThread();
    private final M expector;
    /* guarded by instance */
    private M mock;
    /* current/previous expectation only accessible from createdFrom thread */
    private Expectation current;
    private Expectation previous;
    private volatile ExpectationSequence sequence;

    private final List<Expectable> expectations = new CopyOnWriteArrayList<Expectable>();
    private final List<Injection> injections = new CopyOnWriteArrayList<Injection>();

    private volatile ExpectationFailure failure;
    private volatile MockControl<?> next;

    /**
     * Representation of a call to expects()/allows(), saved to thread local storage in order to be
     * later associated with a stub from Mocks.stub().  This is placed on a stack in order to allow
     * method calls that return a stub value to also define expectation/stub pairings within.
     */
    private static class CurrentControlElement
    {
        private final String className;
        private final String methodName;
        private MockControl<?> control;

        public CurrentControlElement(StackTraceElement element, MockControl<?> control)
        {
            this.className = element.getClassName();
            this.methodName = element.getMethodName();
            this.control = control;
        }

        public boolean isSameMethodAs(StackTraceElement element)
        {
            return className.equals(element.getClassName()) && methodName.equals(element.getMethodName());
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = 1;
            result = prime * result + className.hashCode();
            result = prime * result + methodName.hashCode();
            return result;
        }
        @Override
        public boolean equals(Object obj)
        {
            if (getClass() != obj.getClass())
                return false;

            CurrentControlElement that = (CurrentControlElement)obj;
            return this.className.equals(that.className) && this.methodName.equals(that.methodName);
        }
    }

    static MockControl<?> current()
    {
        LinkedList<CurrentControlElement> stack = currentControl.get();
        if (stack == null)
            return null;

        StackTraceElement callingElement = getCallingElement();

        while (!stack.isEmpty())
        {
            CurrentControlElement next = stack.getLast();
            if (next.isSameMethodAs(callingElement))
                return next.control;
            stack.removeLast();
        }

        return null;
    }

    private void setCurrent()
    {
        LinkedList<CurrentControlElement> stack = currentControl.get();
        if (stack == null)
        {
            stack = new LinkedList<CurrentControlElement>();
            currentControl.set(stack);
        }

        StackTraceElement callingElement = getCallingElement();

        if (!stack.isEmpty())
        {
            for (Iterator<CurrentControlElement> i = stack.iterator(); i.hasNext(); )
            {
                CurrentControlElement next = i.next();
                if (next.isSameMethodAs(callingElement))
                {
                    next.control = this;
                    /* pop rest of stack */
                    while (i.hasNext())
                    {
                        i.next();
                        i.remove();
                    }
                    return;
                }
            }

            /* if control element stack contains frame from execution stack, push this element */
            for (StackTraceElement next : new Exception().getStackTrace())
            {
                if (stack.getLast().isSameMethodAs(next))
                {
                    stack.addLast(new CurrentControlElement(callingElement, this));
                    return;
                }
            }
        }

        /* calling element not on stack */
        stack.clear();
        stack.addLast(new CurrentControlElement(callingElement, this));
    }

    private static StackTraceElement getCallingElement()
    {
        for (StackTraceElement element : new Exception().getStackTrace())
        {
            if (element.getClassName().endsWith("org.mockfu.MockControl")
                    || element.getClassName().endsWith("org.mockfu.Mocks"))
                continue;
            return element;
        }
        return null;
    }

    /**
     * Intercepts expects() calls.
     */
    private class ExpectInterceptor implements MethodInterceptor
    {
        public Object intercept(InvocationContext context) throws Throwable
        {
            try
            {
                if (createdFrom != Thread.currentThread())
                    throw new IllegalStateException("Cannot define expectations in different thread than"
                                                    + " control object was created from");

                current.configure(context.getMethod(), Constraints.getMatchers(context.getArguments()));
                if (!Constraints.current().isEmpty())
                    throw new IllegalArgumentException("Constraints eq, is, lt, leq, gt, and geq"
                            + " cannot nest other constraints, method: " + current);

                if (expectations.contains(current))
                    throw new IllegalStateException("Duplicate expectation \"" + current + "\" and no current sequence");

                if (sequence != null)
                    sequence.add(current);
                else
                    expectations.add(current);

                previous = current;
                return null;
            }
            finally
            {
                current = null;
                Constraints.current().clear();
            }
        }
    }

    static class MockInterceptor implements MethodInterceptor
    {
        /* the injection created created by this or a parent thread */
        private final InheritableThreadLocal<Injection> injection = new InheritableThreadLocal<Injection>();
        /* non-null if current thread is a child of thread that created the mock control */
        private final InheritableThreadLocal<Boolean> isBase = new InheritableThreadLocal<Boolean>();
        private final MockControl<?> baseControl;
        private volatile Injection baseInjection;
        /* the number of injections in thread local storage */
        private int injectionCount;

        public MockInterceptor(MockControl<?> control)
        {
            this.baseControl = control;
            isBase.set(true);
        }

        synchronized void setInjection(Injection injection)
        {
            if (this.injection.get() != null)
                throw new IllegalStateException("\"" + injection + "\" is already injected");

            if (isBase())
                baseInjection = injection;

            this.injection.set(injection);
            injectionCount++;
        }

        synchronized boolean isFieldEjectable()
        {
            return injectionCount == 0;
        }

        synchronized void removeInjection()
        {
            if (injection.get() != null)
            {
                injection.remove();
                injectionCount--;
            }
        }

        synchronized Injection getInjection()
        {
            return injection.get();
        }

        synchronized boolean isBase()
        {
            return isBase.get() != null;
        }

        MockControl<?> getControl()
        {
            return baseControl;
        }

        public Object intercept(InvocationContext context) throws Throwable
        {
            try
            {
                Injection injection = getInjection();

                MockControl<?> control;
                Injection localBaseInjection = baseInjection;
                if (injection != null)
                    /* use control injected for this thread */
                    control = injection.getControl();
                else if (localBaseInjection != null)
                    /* mock was not injected by this test or was ejected */
                    return localBaseInjection.invokeOriginal(context);
                else
                    /* this test created this mock */
                    control = this.baseControl;


                /* there's not really any other to put this check ... */
                if (control.sequence != null)
                    throw new IllegalStateException("Unended expectation sequence");

                try
                {
                    /* since null is a valid stub value, NO_MATCH indicates that no expectation was matched */
                    Object lastResult = Expectable.NO_MATCH;
                    Throwable lastException = null;

                    /* try all expectations, return stub result of last expectation that matched */
                    for (int i = control.expectations.size()-1; i >= 0; i--)
                    {
                        Expectable expectation = control.expectations.get(i);
                        boolean haveMatch = lastResult != Expectable.NO_MATCH || lastException != null;

                        if (expectation.isAllows() && haveMatch)
                            /* skip "allows" expectations if we already have a match */
                            continue;

                        try
                        {
                            Object result = expectation.match(context);
                            if (result != Expectable.NO_MATCH && !haveMatch)
                            {
                                lastResult = result;
                                lastException = null;
                            }
                        }
                        catch (Throwable t)
                        {
                            if (!haveMatch)
                            {
                                lastException = t;
                                lastResult = Expectable.NO_MATCH;
                            }
                        }
                    }
                    /* if we got a stub result, return/throw it */
                    if (lastException != null)
                        throw lastException;

                    if (lastResult != Expectable.NO_MATCH)
                        return lastResult;

                    if (context.getMethod().getName().equals("finalize") && context.getArguments().length == 0)
                        /* finalizer gets called normally by GC, safe to ignore */
                        return null;

                    /* no expectations were matched */
                    throw new ExpectationFailure(context);
                }
                catch (ExpectationFailure ex)
                {
                    if (!context.isIncrementable())
                        /* internal exception, just throw it */
                        throw ex;

                    /* always throw the first failure encountered, since if we're still executing the
                     * first failure was incorrectly ignored by user and the new error is a red herring */
                    if (control.failure == null)
                    {
                        beautifyStackTrace(ex, context.getMethod(), "Mock");
                        control.failure = ex;
                    }

                    if (control.createdFrom != Thread.currentThread())
                    {
                        /* log failure to stderr if this is not the test runner thread, so it doesn't get lost */
                        StringWriter sw = new StringWriter();
                        sw.append("Expectation failure in thread \"");
                        sw.append(Thread.currentThread().toString());
                        sw.append("\":\n");
                        ex.printStackTrace(new PrintWriter(sw));
                        /* do a single write so it doesn't get interrupted */
                        System.err.println(sw.toString());
                        /* no reason for test to continue */
//                        control.createdFrom.interrupt();
                    }
                    throw control.failure;
                }
            }
            finally
            {
                Constraints.current().clear();
            }

        }
    }

    /**
     * Creates a control object for building and verifying the expectations of a particular mock.
     *
     * @param <M>
     * @param klass the class or interface to implement with the mock
     * @return the mock control
     */
    public static <M> MockControl<M> of(Class<M> klass)
    {
        return new MockControl<M>(klass);
    }

    /**
     * Creates a control object for building and verifying the expectations of a particular mock.
     *
     * @param type the class or interface to implement with the mock
     */
    public MockControl(Class<M> type)
    {
        this.klass = type;
        this.expector = ProxyBuilder.of(klass, new ExpectInterceptor()).create();
        if (lastCreated.get() != null)
        {
            lastCreated.get().next = this;
        }
        lastCreated.set(this);
    }

    /**
     * Allows subclasses with generic type parameters to create a control object for a mock with
     * that type. For example, the following declaration creates a control for a mock implementing
     * the <tt>List</tt> interface without causing a raw type compiler warning:
     *
     * <pre>
     * MockControl&lt;List&lt;String&gt;&gt; list = new MockControl&lt;List&lt;String&gt;&gt;() {};
     * </pre>
     */
    @SuppressWarnings("unchecked")
    protected MockControl()
    {
        Type type = this.getClass().getGenericSuperclass();
        if (type instanceof Class)
            throw new RuntimeException("Missing type parameter");
        type = ((ParameterizedType)type).getActualTypeArguments()[0];
        if (type instanceof ParameterizedType)
            this.klass = (Class<M>)((ParameterizedType)type).getRawType();
        else
            this.klass = (Class<M>)type;
        this.expector = ProxyBuilder.of(klass, new ExpectInterceptor()).create();

        if (lastCreated.get() != null)
        {
            lastCreated.get().next = this;
        }
        lastCreated.set(this);
    }

    public Class<M> type()
    {
        return klass;
    }

    /**
     * Invokes the superclass constructor whose signature corresponds to <tt>argTypes</tt>.
     *
     * @param argTypes the argument classes specifying the constructor
     * @param args the arguments to pass to the constructor
     */
    public void invokeConstructor(Class<?>[] argTypes, Object... args)
    {
        if (createdFrom != Thread.currentThread())
            throw new IllegalStateException("Cannot invoke constructor in different thread than"
                                            + " control object was created from");

        M localMock;
        synchronized (this)
        {
            if (mock != null)
                throw new IllegalStateException("Cannot invoke constructor because mock has already been instantiated");
            localMock = mock;
        }

        /* don't lock while invoking constructor */
        localMock = ProxyBuilder.of(klass, new MockInterceptor(this)).create(argTypes, args);

        synchronized (this)
        {
            /* double-check that the mock wasn't instantiated by another thread (or by the constructor) */
            if (mock != null)
                throw new IllegalStateException("Cannot return from constructor because mock has already been instantiated");
            mock = localMock;
        }
    }

    /**
     * Invokes the superclass constructor whose signature matches the types of <tt>args</tt>.
     *
     * @param args the arguments to pass to the constructor
     */
    public void invokeConstructor(Object... args)
    {
        invokeConstructor(null, args);
    }


    /**
     * Indicates that any method may be called on the mock any number of times and with any
     * arguments. Stubs may be assigned to any non-private, non-final method of the class after
     * this has been called.
     */
    public void allowsAnything()
    {
        setCurrent();
        previous = null;
        current = null;

        List<Expectation> newExpectations = new ArrayList<Expectation>();
        for (Method method : getMethodsIn(klass))
        {
            List<Matcher<Object>> matchers = new ArrayList<Matcher<Object>>();
            int params = method.getParameterTypes().length;
            for (int i = 0 ; i < params ; i++)
                matchers.add(new Mocks.AnythingMatcher("any"));
            Expectation expectation = new Expectation(new Multiplicity(0, Integer.MAX_VALUE));
            expectation.configure(method, matchers);
            newExpectations.add(expectation);
        }
        expectations.addAll(newExpectations);
    }

    /**
     * Indicates that any method in the class <tt>type</tt> may be called on the mock any number
     * of times and with any arguments. Stubs may be assigned to any non-private, non-final method
     * in the specified class after this has been called.
     */
    public void allowsAnythingIn(Class<?> type)
    {
        setCurrent();
        previous = null;
        current = null;


        Set<Method> controlMethods = getMethodsIn(klass);
        Set<Method> typeMethods = getMethodsIn(type);

        List<Expectation> newExpectations = new ArrayList<Expectation>();
        for (Method method : controlMethods)
        {
            boolean found = false;
            for (Method each : typeMethods)
                if (each.getName().equals(method.getName())
                        && Arrays.equals(each.getParameterTypes(), method.getParameterTypes()))
                {
                    found = true;
                    break;
                }
            if (!found)
                continue;

            List<Matcher<Object>> matchers = new ArrayList<Matcher<Object>>();
            int params = method.getParameterTypes().length;
            for (int i = 0 ; i < params ; i++)
                matchers.add(new Mocks.AnythingMatcher("any"));
            Expectation expectation = new Expectation(new Multiplicity(0, Integer.MAX_VALUE));
            expectation.configure(method, matchers);
            newExpectations.add(expectation);
        }
        expectations.addAll(newExpectations);
    }

    private Set<Method> getMethodsIn(Class<?> klass)
    {
        Set<Method> methods = new LinkedHashSet<Method>();

        for (Class<?> nextClass = klass; nextClass != null; nextClass = nextClass.getSuperclass())
        {
            declaredMethods:
            for (Method method : nextClass.getDeclaredMethods())
            {
                if ((method.getModifiers() & (Modifier.PRIVATE|Modifier.FINAL|Modifier.STATIC)) == 0)
                {
                    for (Method existing : methods)
                        if (existing.getName().equals(method.getName())
                                && Arrays.equals(existing.getParameterTypes(), method.getParameterTypes()))
                            continue declaredMethods;
                    methods.add(method);
                }
            }
        }
        return methods;
    }

    /**
     * Begins an expectation sequence in which all mock calls must occur in the exact order in which
     * expectations are declared, until ended by a call to <tt>{@link #endSequence()}</tt>.
     * <p>
     * Besides enforcing the ordering of mock object method calls, a sequence makes it possible to
     * stub expectation matches differently on subsequent invocations. For example, the following
     * returns <tt>10</tt> the first three calls to <tt>method</tt>, followed by 20 the next
     * call, followed by an exception in any later call:
     *
     * <pre>
     * obj.beginSequence();
     * stub(obj.expects(3).method(), 10);
     * stub(obj.expectOnce().method(), 20);
     * obj.expects().method();
     * obj.stubWithException(new IllegalStateException(&quot;Eeeps!&quot;));
     * obj.endSequence();
     * </pre>
     *
     * Variable expectations are matched reluctantly, in that an expectation that can match a
     * variable number of times will match as many times as possible before moving on to the next
     * expectation in the sequence. For example, the sequence
     *
     * <pre>
     * obj.expectOnce().method();
     * obj.expects().method();
     * </pre>
     *
     * will verify following a single invocation of <tt>method</tt>, but the sequence
     *
     * <pre>
     * obj.expects().method();
     * obj.expectOnce().method();
     * </pre>
     *
     * will not, because the second expectation will never be reached.
     */
    public void beginSequence()
    {
        if (sequence != null)
            throw new IllegalStateException("Current sequence must be ended before starting a " + "new one");
        sequence = new ExpectationSequence();
        expectations.add(sequence);
    }

    /**
     * Ends an expectation sequence started by a call to <tt>{@link #beginSequence()}</tt>.
     */
    public void endSequence()
    {
        if (sequence == null)
            throw new IllegalStateException("No active sequence");
        sequence = null;
    }

    /**
     * Returns an instance of the mock class on which an expectation is defined by calling the
     * expected method with the expected arguments or constraints as parameters. A call on the mock
     * matching the expectation is allowed to execute any number of times, or not at all.
     *
     * @return an object on which to define expectations
     */
    public M allows()
    {
        return expects(new Multiplicity(0, Integer.MAX_VALUE));
    }

    /**
     * Returns an instance of the mock class on which an expectation is defined by calling the
     * expected method with the expected arguments or constraints as parameters. A call on the mock
     * matching the expectation must execute exactly <tt>times</tt> times.
     *
     * @param times the exact number of calls the expectation must match to verify
     * @return an object on which to define an expectation
     */
    public M expects(int times)
    {
        return expects(new Multiplicity(times, times));
    }

    /**
     * Returns an instance of the mock class on which an expectation is defined by calling the
     * expected method with the expected arguments or constraints as parameters. A call on the mock
     * matching the expectation must execute exactly once.
     *
     * @return an object on which to define an expectation
     */
    public M expectsOnce()
    {
        return expects(new Multiplicity(1, 1));
    }

    /**
     * Returns an instance of the mock class on which an expectation is defined by calling the
     * expected method with the expected arguments or constraints as parameters. A call on the mock
     * matching the expectation must execute at least <tt>times</tt> times.
     *
     * @param times the minimum number of calls the expectation must match to verify
     * @return an object on which to define an expectation
     */
    public M expectsAtLeast(int times)
    {
        return expects(new Multiplicity(times, Integer.MAX_VALUE));
    }

    /**
     * Returns an instance of the mock class on which an expectation is defined by calling the
     * expected method with the expected arguments or constraints as parameters. A call on the mock
     * matching the expectation must execute at least <tt>times</tt> times.
     *
     * @param times the maximum number of calls the expectation must match to verify
     * @return an object on which to define an expectation
     */
    public M expectsAtMost(int times)
    {
        return expects(new Multiplicity(0, times));
    }

    private M expects(Multiplicity multiplicity)
    {
        if (createdFrom != Thread.currentThread())
            throw new IllegalStateException("Cannot define expectations in different thread than"
                                            + " control object was created from");
        setCurrent();
        current = new Expectation(multiplicity);
        return expector;
    }

    /**
     * Stubs the last expectation defined with a custom stub handler. The method
     * <tt>{@link #stubWithDelegate(Object)}</tt> should normally be used in preference to this
     * since it is type safe, but this method is available for situations in which a direct subclass
     * of the mock type is not possible. Also in contrast to
     * <tt>{@link #stubWithDelegate(Object)}</tt> this method will stub only the last defined
     * expectation, rather than all previous expectations that are currently unstubbed.
     * <p>
     * The stub will be executed any time an expectation matches a method call on the mock. An
     * expectation match can be canceled by throwing an <tt>{@link UnmatchedException}</tt> from
     * within the stub, in which case the expectation's invocation count will not increment and if
     * the call matches no other expectations an assertion error will be thrown indicating an
     * unexpected method call.
     * <p>
     * If multiple stubbed expectations match the method call, all stubs will execute but only the
     * return value of the stub for the most recently defined expectation will be returned.
     *
     * @param stub a custom stub handler
     * @throws IllegalStateException if there is no last expectation or it is already stubbed
     */
    public void stubLastWith(Stub<?> stub)
    {
        if (previous == null)
            throw new IllegalStateException("No expectation to stub (is method final?)");

        if (!previous.stubWithIfPossible(stub, null))
            throw new IllegalStateException("Cannot stub previous expectation: "
                                            + "it is already stubbed or stub does not apply");
    }

    /**
     * Stubs any currently unstubbed expectations with a method implemented in <tt>impl</tt> that
     * matches the method signature of an expected method. The stub may be a subclass of the mock
     * type, implement a common interface, or just contain matching method signatures. The stub may
     * implement multiple methods, in which case each method will be assigned to one or more
     * expectations. If an expectation is currently unstubbed and the expected method is not present
     * in the stub, that expectation will remain unstubbed.
     * <p>
     * The stub method will be executed any time an expectation matches a method call on the mock.
     * An expectation match can be canceled by throwing an <tt>{@link UnmatchedException}</tt>
     * from within the stub, in which case expectation's invocation count will not increment and if
     * the call matches no other expectations an assertion error will be thrown indicating an
     * unexpected method call.
     * <p>
     * If multiple stubbed expectations match the method call, all stubs will execute but only the
     * return value of the stub for the most recently defined expectation will be returned.
     *
     * @param impl an object containing method signatures
     * @throws IllegalStateException if no expectations can be stubbed
     */
    public void stubWithDelegate(final Object impl)
    {

        boolean stubbed = false;

        for (final Method method : impl.getClass().getMethods())
        {
            /* ignore private and synthetic methods */
            if (Modifier.isPrivate(method.getModifiers()) || (method.getModifiers() & 0x1000) != 0)
                continue;

            Stub<?> stub = new Stub<Object>() {
                public Object invoke(InvocationContext context) throws Throwable
                {
                    try
                    {
                        method.setAccessible(true);
                        return method.invoke(impl, context.getArguments());
                    }
                    catch (InvocationTargetException ex)
                    {
                        /* unwrap exceptions thrown by stub implementation */
                        throw ex.getCause();
                    }
                }
            };

            for (Expectable expectation : expectations)
                stubbed |= expectation.stubWithIfPossible(stub, new EqualsMethodFilter(method));
        }

        if (!stubbed)
        {
            throw new IllegalStateException("No expectations were stubbed");
        }
    }

    /**
     * Stubs any currently unstubbed expectations with a method implemented in <tt>impl</tt> that
     * matches the method signature of an expected method. The stub may implement multiple methods,
     * in which case each method will be assigned to one or more expectations. The stub class must
     * be a subclass of the mock type, and only methods declared on the stub class (not inherited)
     * will be stubbed. If an expectation is currently unstubbed and the expected method is not
     * present in the stub, that expectation will remain unstubbed.
     * <p>
     * The stub method will be executed any time an expectation matches a method call on the mock.
     * An expectation match can be canceled by throwing an <tt>{@link UnmatchedException}</tt>
     * from within the stub, in which case expectation's invocation count will not increment and if
     * the call matches no other expectations an assertion error will be thrown indicating an
     * unexpected method call.
     * <p>
     * If multiple stubbed expectations match the method call, all stubs will execute but only the
     * return value of the stub for the most recently defined expectation will be returned.
     *
     * @param impl an implementation of the mock type containing stub handlers
     * @throws IllegalStateException if a non-private method on the stub class doesn't have a
     *             currently defined expectation
     */
    public void stubWith(final M impl)
    {

        for (final Method method : impl.getClass().getDeclaredMethods())
        {
            /* ignore private and synthetic methods */
            if (Modifier.isPrivate(method.getModifiers()) || (method.getModifiers() & 0x1000) != 0)
                continue;

            Stub<?> stub = new Stub<Object>() {
                public Object invoke(InvocationContext context) throws Throwable
                {
                    try
                    {
                        method.setAccessible(true);
                        return method.invoke(impl, context.getArguments());
                    }
                    catch (InvocationTargetException ex)
                    {
                        /* unwrap exceptions thrown by stub implementation */
                        throw ex.getCause();
                    }
                }
            };

            boolean stubbed = false;

            for (Expectable expectation : expectations)
                stubbed |= expectation.stubWithIfPossible(stub, new EqualsMethodFilter(method));

            if (!stubbed)
            {
                StringBuilder methodName = new StringBuilder(method.getName());
                methodName.append("(");
                for (Class<?> type : method.getParameterTypes())
                {
                    if (methodName.charAt(methodName.length() - 1) != '(')
                        methodName.append(",");
                    methodName.append(type.getSimpleName());
                }
                methodName.append(")");
                throw new IllegalStateException("No expectation to stub for method " + methodName);
            }
        }
    }

    private static class EqualsMethodFilter implements MethodFilter
    {
        private final Method stubMethod;

        public EqualsMethodFilter(final Method stubMethod)
        {
            this.stubMethod = stubMethod;
        }

        public boolean matches(Method expectMethod)
        {
            /* only assign stub if the stub method overrides the expect method */
            Class<?>[] stubParamTypes = stubMethod.getParameterTypes();
            Class<?>[] expectParamTypes = expectMethod.getParameterTypes();

            if (stubParamTypes.length == expectParamTypes.length && stubMethod.getName().equals(expectMethod.getName()))
            {
                Type[] genericParamTypes = expectMethod.getGenericParameterTypes();

                for (int i = 0 ; i < expectParamTypes.length ; i++)
                {
                    if (genericParamTypes[i] instanceof TypeVariable)
                    {
                        /* parameter is generic, subclass type must be assignable */
                        if (!expectParamTypes[i].isAssignableFrom(stubParamTypes[i]))
                            return false;
                    }
                    else
                    {
                        /* parameter is not generic, types must be the same */
                        if (!stubParamTypes[i].equals(expectParamTypes[i]))
                            return false;
                    }
                }
                return true;
            }
            return false;
        }
    }

    /**
     * Stubs any currently unstubbed expectations with a handler that throws the given exception.
     * The exception may be checked even if the method does not declare that exception as being
     * thrown. If the exception is checked but not declared and you wish to catch it explicitly
     * higher in the stack, use the method <tt>{@link Exceptions#declare(Class)}</tt> to allow the
     * catch clause to compile.
     * <p>
     * If multiple stubbed expectations match the method call, all stubs will execute but only the
     * return value or exception of the stub for the most recently defined expectation will be
     * returned or thrown.
     *
     * @param ex exception to throw
     * @throws IllegalStateException if no expectations can be stubbed
     */
    public void stubWithException(final Throwable ex)
    {
        boolean stubbed = false;

        Stub<?> stub = new Stub<Object>() {
            public Object invoke(InvocationContext context) throws Throwable
            {
                /* generate a nicer stack trace */
                beautifyStackTrace(ex, context.getMethod(), "Exception Stub");
                throw ex;
            }
        };

        for (Expectable expectation : expectations)
            stubbed |= expectation.stubWithIfPossible(stub, null);

        if (!stubbed)
            throw new IllegalStateException("No expectations were stubbed");
    }

    /**
     * Stubs any currently unstubbed expectations with a call to the super class (that is, a call to
     * the class this control object was created with). If a super class constructor is not invoked
     * from this control object before the stub is used that invocation will throw an exception.
     *
     * @throws IllegalStateException if control object was created with an interface
     */
    public void stubWithSuper()
    {
        if (klass.isInterface())
            throw new IllegalStateException("Cannot stub with super because " + klass.getName() + " is an interface");

        boolean stubbed = false;

        Stub<?> stub = new Stub<Object>() {
            public Object invoke(InvocationContext context) throws Throwable
            {
                return context.invokeSuper();
            }
        };

        for (Expectable expectation : expectations)
            stubbed |= expectation.stubWithIfPossible(stub, new MethodFilter() {

                public boolean matches(Method method)
                {
                    return (method.getModifiers() & Modifier.ABSTRACT) == 0;
                }
            });

        if (!stubbed)
            throw new IllegalStateException("No expectations were stubbed");
    }

    /**
     * Stubs any unstubbed expectations with a stub that does nothing. Since this stub is equivalent
     * to the unstubbed behavior of an expectation, you would only use this to prevent another stub
     * call from stubbing certain expectations.
     */
    public void stubWithVoid()
    {
        boolean stubbed = false;

        Stub<?> stub = new Stub<Object>() {
            public Object invoke(InvocationContext context) throws Throwable
            {
                return null;
            }
        };

        for (Expectable expectation : expectations)
            stubbed |= expectation.stubWithIfPossible(stub, null);

        if (!stubbed)
            throw new IllegalStateException("No expectations were stubbed");
    }

    /**
     * Returns the mock object.
     *
     * @return the mock object
     */
    public M mock()
    {
        current = null;
        synchronized (this)
        {
            if (mock == null)
                mock = ProxyBuilder.of(klass, new MockInterceptor(this)).setName("Mock").create();
            return mock;
        }
    }

    /**
     * Injects the mock object into another object by its type. That is, if a field in <tt>unit</tt>
     * (private or not) is assignable from the type of the mock, that field will be assigned the
     * mock object. If multiple fields are assignable from the mock a <tt>NoSuchFieldException</tt>
     * is thrown.
     *
     * @param unit the object to inject this mock into
     * @throws NoSuchFieldException if no or multiple fields are equally assignable within the unit
     */
    public void injectInto(Object unit)
    {
        Injection injection = new Injection(unit, mock());
        injection.inject();
        injections.add(injection);
    }

    /**
     * Injects the mock object into another object by field name. The injected field need not be
     * public or have accessor methods.
     *
     * @param unit the object to inject this mock into
     * @param fieldName the field to inject this mock into
     * @throws NoSuchFieldException if a field cannot be found by that name
     */
    public void injectInto(Object unit, String fieldName)
    {
        Injection injection = new Injection(unit, fieldName, mock());
        injection.inject();
        injections.add(injection);
    }

    /**
     * Injects the mock object into a class by its type. That is, if a static field in
     * <tt>klass</tt> (private or not) is assignable from the type of the mock, that field will be
     * assigned the mock object. If multiple fields are assignable from the mock a
     * <tt>NoSuchFieldException</tt> is thrown.
     *
     * @param klass the class to inject this mock into
     * @throws NoSuchFieldException if no or multiple fields are equally assignable within the class
     */
    public void injectInto(Class<?> klass)
    {
        Field match = null;
        for (Field field : klass.getDeclaredFields())
        {
            if (field.getType() == this.klass && (field.getModifiers() & Modifier.STATIC) != 0)
            {
                if (match != null)
                    throw undeclared(new NoSuchFieldException("Cannot inject mock by "
                            + "type because multiple fields are of type " + klass));
                match = field;
            }
        }

        Injection injection = null;
        if (match != null)
            injection = new Injection(klass, match.getName(), mock());
        else
            /* try to inject by assignability */
            injection = new Injection(klass, mock());
        injection.inject();
        injections.add(injection);
    }

    /**
     * Injects the mock object into a class by field name. The injected field need not be public or
     * have accessor methods.
     *
     * @param klass the class to inject this mock into
     * @param fieldName the field to inject this mock into
     * @throws NoSuchFieldException if a field cannot be found by that name
     */
    public void injectInto(Class<?> klass, String fieldName)
    {
        Injection injection = new Injection(klass, fieldName, mock());
        injection.inject();
        injections.add(injection);
    }

    /**
     * Removes any mocks from the objects or classes this control injected into, replacing the mock
     * with the value of the field before injection.
     */
    public void eject()
    {
        for (Injection injection : injections)
            injection.eject();
    }


    /**
     * Verifies that all expectations defined on this mock were met.
     *
     * @throws ExpectationFailure if an expectation was not met
     */
    public void verify()
    {
        if (sequence != null)
            throw new IllegalStateException("Unended expectation sequence");

        /* an in-unit failure might have been thrown in another thread or inadvertently ignored,
         * throw it here so it doesn't get lost */
        if (failure != null)
            throw failure;

        for (Expectable expectation : expectations)
        {
            expectation.verify();
        }
    }

    /**
     * Verifies this mock and all mocks that were created or reset following this one.
     *
     * @throws ExpectationFailure if an expectation was not met
     */
    public void verifyAll()
    {
        for (MockControl<?> each = this; each != null; each = each.next)
            each.verify();
    }

    /**
     * Returns the first expectation failure that occurred for this mock or any mock tha was created
     * or reset following this one.  This is intended for use by the automatic verifier.
     *
     * @return
     */
    public ExpectationFailure getFirstFailure()
    {
        ExpectationFailure failure = null;
        for (MockControl<?> each = this; each != null; each = each.next)
            if ((failure = each.failure) != null)
                break;
        return failure;
    }


    /**
     * Resets the state of all expectations defined on this mock as if no expectations have yet been
     * matched.
     */
    public void reset()
    {
        for (Expectable expectation : expectations)
            expectation.reset();
    }

    /**
     * Removes all expectations.
     */
    public void clear()
    {
        expectations.clear();
    }

    static void beautifyStackTrace(Throwable ex, Method method, String comment)
    {
        StackTraceElement[] trace = new Exception().getStackTrace();
        int i = 0;
        int start = -1;
        while (i < trace.length)
        {
            String elementClassName = trace[i].getClassName();
            if (start == -1 && elementClassName.contains("org.mockfu."))
                start = i;

            i++;
            if (elementClassName.contains("$$Mock$$") || elementClassName.contains("$$Proxy$$"))
            {
                StackTraceElement[] newTrace = new StackTraceElement[(trace.length - i) + 1];
                String className = method.getDeclaringClass().getCanonicalName();
                newTrace[0] = new StackTraceElement(className, method.getName(), comment, -1);
                for (int j = 1 ; j < newTrace.length ; j++)
                    newTrace[j] = trace[i++];
                ex.setStackTrace(newTrace);
                break;
            }
        }
    }
}
